package com.szrt.spring.ch11.ioc;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.beanutils.ConvertUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

import com.szrt.spring.ch11.annotation.ScottResource;
public class ScottClassPathXmlApplicationContext implements ScottApplicationContext {
	
	/**
	 * 在一个配置文件中可以配置多个<bean>标签
	 */
	private List<Bean> beans = new ArrayList<Bean>();
	
	/**
	 * maps   
	 * 		key:   bean的id属性:String
	 * 		value: bean的class属性对应的类的对象: Object
	 */
	private Map<String, Object> maps = new HashMap<String, Object>();
	
	public ScottClassPathXmlApplicationContext(String configLocation){
		
		dom4jReadXML(configLocation);
		
		createObject();
		
		//使用注解方式注入
		annotationInject();
		
		injectObject();
		
	}
	
	
	/**
	 * 使用注解方式注入依赖对象
	 */
	private void annotationInject(){
		
		//循环所有的bean对象
		//<bean id="studentService" class="com.szrt.spring.ch10.student.service.impl.StudentServiceImpl"></bean>
		Set<String> ids = maps.keySet();
		for(String id : ids){
			
			Object clazz = maps.get(id);
			if(null != clazz){
				try {
					PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz.getClass()).getPropertyDescriptors();
					
					//1 注解加在set方法上
					for(PropertyDescriptor pd :pds){
						//取得属性的set方法   并赋值  setStudentDao  setName  setId
						Method setter = pd.getWriteMethod();
						if(null != setter){
							//判断set方法上是否有注解 @ScottResource
							if(setter.isAnnotationPresent(ScottResource.class)){
								//存在注解
								ScottResource resource = setter.getAnnotation(ScottResource.class);
								Object value = null;
								String name = resource.name(); //
								if(null != name  &&  !"".equals(name)){
									//1 根据属性的名称去寻找  说明有指定name属性的值  @ScottResource(name="studentDao")
									value = maps.get(name);
								}else{
									//2 如果没有寻找到 就根据类型去寻找
									//@ScottResource
									//取得属性的名称
									String attributeName = pd.getName();
									value = maps.get(attributeName);
									if(null == value){
										//说明根据名称找不到 需要根据类型查找
										Set<String> keys = maps.keySet();
										for(String key : keys){
											
											if(pd.getPropertyType().isAssignableFrom(maps.get(key).getClass())){
												//说明2者的类型匹配
												value = maps.get(key);
												break;
											}
										}
									}
								}
								setter.setAccessible(true);
								setter.invoke(clazz, value);
							}
							
							
						}
					}
					
					//2 注解加在属性上
					Field[] fields = clazz.getClass().getDeclaredFields();
					for(Field field : fields){
						
						if(field.isAnnotationPresent(ScottResource.class)){
							//属性上存在注解@ScottResource
							ScottResource resource = field.getAnnotation(ScottResource.class);
							Object value = null;
							String name = resource.name(); //
							if(null != name  &&  !"".equals(name)){
								//1 根据属性的名称去寻找  说明有指定name属性的值  @ScottResource(name="studentDao")
								value = maps.get(name);
							}else{
								//2 如果没有寻找到 就根据类型去寻找
								//@ScottResource
								//取得属性的名称
								String attributeName = field.getName();
								value = maps.get(attributeName);
								if(null == value){
									//说明根据名称找不到 需要根据类型查找
									Set<String> keys = maps.keySet();
									for(String key : keys){
										
										if(field.getType().isAssignableFrom(maps.get(key).getClass())){
											//说明2者的类型匹配
											value = maps.get(key);
											break;
										}
									}
								}
							}
							field.setAccessible(true);
							field.set(clazz, value);
						}
					}
					
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		}
		
	}
	

	
	/**
	 * 为bean对象的属性注入值
	 */
	private void injectObject() {
		
		for(Bean bean :beans){
			
			Object beanObject =maps.get(bean.getId());
			
			if(null != beanObject){
				
				try {
					PropertyDescriptor[] pds = Introspector.getBeanInfo(beanObject.getClass()).getPropertyDescriptors();
					//循环bean下的所有property属性
					for(Property property : bean.getPropertyList()){
						for(PropertyDescriptor pd :pds){
							
							if(pd.getName().equals(property.getName())){
								
								//取得属性的setter方法并赋值  setStudentDao  setName  setId
								Method setter = pd.getWriteMethod();
								System.err.println(setter);
								
								
								if(setter != null){
									
									//2.1 注入引用对象的值
									String ref = property.getRef();
									if(null != ref && !"".equals(ref.trim()) ){
										//property标签中ref的值和另外1个bean的id属性的值一样  ref="studentDao"
										Object value = maps.get(ref);
										setter.setAccessible(true);
										//public void setStudentDao(StudentDao studentDao)
										
										setter.invoke(beanObject, value);
									}
									
									
									//2.2 注入普通属性的值
									String value =property.getValue();
									if(null != value  && !"".equals(value.trim())){
										//把字符串类型的值转化成属性对应的类型的值  commons-beanutils
										//<property name="id"   value="1001001"></property>  private Integer id;
										//<property name="name" value="scott"></property>    private String name;
										
										Object object =ConvertUtils.convert(value, pd.getPropertyType());
										System.out.println(object);
										
										setter.setAccessible(true);
										//public void setStudentDao(StudentDao studentDao)
										setter.invoke(beanObject, object);
									}
									
									
								}
								
								
							}
						}
					}
					
					
				} catch (Exception e) {
					e.printStackTrace();
				}
				
				
			}
		}
		
		
	}

	/**
	 * 读取到配置文件之后 利用反射实例化具体的对象
	 */
	private void createObject() {
		
		for(Bean bean :beans){
			
			try {
				
				if(null !=bean.getClazz()  && !"".equals(bean.getClazz())){
					//根据字符串实例化具体的对象
					Class<?> clazz = Class.forName(bean.getClazz());
					Object object = clazz.newInstance();
					maps.put(bean.getId(), object);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} 
		}
		
		
		
	}

	/**
	 * 利用dom4j读取配置文件 applicationContext07.xml
	 * @param configLocation
	 */
	@SuppressWarnings("unchecked")
	private void dom4jReadXML(String configLocation) {
		
		//创建1个xml文件读取器
		SAXReader reader  = new SAXReader();
		
		Document document = null;
		
		try {
			
			//得到xml文件
			URL xmlPath = this.getClass().getClassLoader().getResource(configLocation);
			document = reader.read(xmlPath);
			
			Map<String, String> namespace = new HashMap<String, String>();
			
			namespace.put("xmlns", "http://www.springframework.org/schema/beans");
			
			//创建beans/bean查询路径
			XPath createXPath = document.createXPath("//xmlns:beans/xmlns:bean");
			
			//设置命名空间
			createXPath.setNamespaceURIs(namespace);
			
			//获取文档下所有的bean节点
			List<Element> beanList =createXPath.selectNodes(document);
			
			for(Element beanElement :beanList){
				//获取id属性的值
				String id = beanElement.attributeValue("id");
				
				//获取class属性的值
				String clazz = beanElement.attributeValue("class");
				
				Bean bean = new Bean();
				bean.setId(id);
				bean.setClazz(clazz);
				
				//利用相对路径 找到bean下面的property标签
				XPath propertyXPath = beanElement.createXPath("xmlns:property");
				propertyXPath.setNamespaceURIs(namespace);
				
				//获取bean标签下的所有property标签
				List<Element> propertyNodes = propertyXPath.selectNodes(beanElement);
				
				for(Element propertyElement:propertyNodes){
					
					//取得property标签中的name和ref属性
					String name  = propertyElement.attributeValue("name");
					String ref   = propertyElement.attributeValue("ref");
					
					//读取value属性的值
					String value = propertyElement.attributeValue("value");
					
					System.out.println(name + " : "+ref);
					
					Property property = new Property();
					property.setName(name);
					property.setRef(ref);
					property.setValue(value);
					
					//把property加入到bean中
					bean.getPropertyList().add(property);
				}
				
				//加入到beans中
				beans.add(bean);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	

	@Override
	public Object getBean(String name) {
		return maps.get(name);
	}

}
